// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Silk.NET.SilkTouch
{
    public static class NativeContextOverrideHelper
    {
        public static MethodDeclarationSyntax GetProcAddress => HelperMethods.Value.GetProcAddress;
        public static MethodDeclarationSyntax TryGetProcAddress => HelperMethods.Value.TryGetProcAddress;
        public static Lazy<(MethodDeclarationSyntax GetProcAddress, MethodDeclarationSyntax TryGetProcAddress)>
            HelperMethods { get; } = new
        (
            () =>
            (
                MethodDeclaration
                    (
                        IdentifierName("System.IntPtr"),
                        Identifier("GetProcAddress")
                    )
                    .WithModifiers
                    (
                        TokenList
                        (
                            Token(SyntaxKind.PublicKeyword)
                        )
                    )
                    .WithParameterList
                    (
                        ParameterList
                        (
                            SeparatedList<ParameterSyntax>
                            (
                                new SyntaxNodeOrToken[]
                                {
                                    Parameter
                                        (
                                            Identifier("proc")
                                        )
                                        .WithType
                                        (
                                            PredefinedType
                                            (
                                                Token(SyntaxKind.StringKeyword)
                                            )
                                        ),
                                    Token(SyntaxKind.CommaToken),
                                    Parameter
                                        (
                                            Identifier("slot")
                                        )
                                        .WithType
                                        (
                                            NullableType
                                            (
                                                PredefinedType
                                                (
                                                    Token(SyntaxKind.IntKeyword)
                                                )
                                            )
                                        )
                                        .WithDefault
                                        (
                                            EqualsValueClause
                                            (
                                                LiteralExpression
                                                (
                                                    SyntaxKind.DefaultLiteralExpression,
                                                    Token(SyntaxKind.DefaultKeyword)
                                                )
                                            )
                                        )
                                }
                            )
                        )
                    )
                    .WithBody
                    (
                        Block
                        (
                            LocalDeclarationStatement
                            (
                                VariableDeclaration
                                    (
                                        IdentifierName("var")
                                    )
                                    .WithVariables
                                    (
                                        SingletonSeparatedList
                                        (
                                            VariableDeclarator
                                                (
                                                    Identifier("pfn")
                                                )
                                                .WithInitializer
                                                (
                                                    EqualsValueClause
                                                    (
                                                        InvocationExpression
                                                            (
                                                                IdentifierName
                                                                (
                                                                    "CoreGetProcAddress"
                                                                )
                                                            )
                                                            .WithArgumentList
                                                            (
                                                                ArgumentList
                                                                (
                                                                    SeparatedList<
                                                                        ArgumentSyntax>
                                                                    (
                                                                        new
                                                                            SyntaxNodeOrToken
                                                                            []
                                                                            {
                                                                                Argument
                                                                                (
                                                                                    IdentifierName
                                                                                    (
                                                                                        "proc"
                                                                                    )
                                                                                ),
                                                                                Token
                                                                                (
                                                                                    SyntaxKind
                                                                                        .CommaToken
                                                                                ),
                                                                                Argument
                                                                                (
                                                                                    IdentifierName
                                                                                    (
                                                                                        "slot"
                                                                                    )
                                                                                )
                                                                            }
                                                                    )
                                                                )
                                                            )
                                                    )
                                                )
                                        )
                                    )
                            ),
                            IfStatement
                            (
                                BinaryExpression
                                (
                                    SyntaxKind.EqualsExpression,
                                    IdentifierName("pfn"),
                                    MemberAccessExpression
                                    (
                                        SyntaxKind.SimpleMemberAccessExpression,
                                        IdentifierName("System.IntPtr"),
                                        IdentifierName("Zero")
                                    )
                                ),
                                Block
                                (
                                    SingletonList<StatementSyntax>
                                    (
                                        ThrowStatement
                                        (
                                            ObjectCreationExpression
                                                (
                                                    IdentifierName
                                                        ("InvalidOperationException")
                                                )
                                                .WithArgumentList
                                                (
                                                    ArgumentList
                                                    (
                                                        SingletonSeparatedList
                                                        (
                                                            Argument
                                                            (
                                                                LiteralExpression
                                                                (
                                                                    SyntaxKind
                                                                        .StringLiteralExpression,
                                                                    Literal
                                                                        ("Invalid slot")
                                                                )
                                                            )
                                                        )
                                                    )
                                                )
                                        )
                                    )
                                )
                            ),
                            ReturnStatement(IdentifierName("pfn"))
                        )
                    ),
                MethodDeclaration
                    (
                        PredefinedType
                        (
                            Token(SyntaxKind.BoolKeyword)
                        ),
                        Identifier("TryGetProcAddress")
                    )
                    .WithModifiers
                    (
                        TokenList
                        (
                            Token(SyntaxKind.PublicKeyword)
                        )
                    )
                    .WithParameterList
                    (
                        ParameterList
                        (
                            SeparatedList<ParameterSyntax>
                            (
                                new SyntaxNodeOrToken[]
                                {
                                    Parameter
                                        (
                                            Identifier("proc")
                                        )
                                        .WithType
                                        (
                                            PredefinedType
                                            (
                                                Token(SyntaxKind.StringKeyword)
                                            )
                                        ),
                                    Token(SyntaxKind.CommaToken),
                                    Parameter
                                        (
                                            Identifier("pfn")
                                        )
                                        .WithModifiers
                                        (
                                            TokenList
                                            (
                                                Token(SyntaxKind.OutKeyword)
                                            )
                                        )
                                        .WithType
                                        (
                                            IdentifierName("System.IntPtr")
                                        ),
                                    Token(SyntaxKind.CommaToken),
                                    Parameter
                                        (
                                            Identifier("slot")
                                        )
                                        .WithType
                                        (
                                            NullableType
                                            (
                                                PredefinedType
                                                (
                                                    Token(SyntaxKind.IntKeyword)
                                                )
                                            )
                                        )
                                        .WithDefault
                                        (
                                            EqualsValueClause
                                            (
                                                LiteralExpression
                                                (
                                                    SyntaxKind.DefaultLiteralExpression,
                                                    Token(SyntaxKind.DefaultKeyword)
                                                )
                                            )
                                        )
                                }
                            )
                        )
                    )
                    .WithExpressionBody
                    (
                        ArrowExpressionClause
                        (
                            BinaryExpression
                            (
                                SyntaxKind.NotEqualsExpression,
                                ParenthesizedExpression
                                (
                                    AssignmentExpression
                                    (
                                        SyntaxKind.SimpleAssignmentExpression,
                                        IdentifierName("pfn"),
                                        InvocationExpression
                                            (
                                                IdentifierName("CoreGetProcAddress")
                                            )
                                            .WithArgumentList
                                            (
                                                ArgumentList
                                                (
                                                    SeparatedList<ArgumentSyntax>
                                                    (
                                                        new SyntaxNodeOrToken[]
                                                        {
                                                            Argument
                                                            (
                                                                IdentifierName("proc")
                                                            ),
                                                            Token
                                                                (SyntaxKind.CommaToken),
                                                            Argument
                                                            (
                                                                IdentifierName("slot")
                                                            )
                                                        }
                                                    )
                                                )
                                            )
                                    )
                                ),
                                DefaultExpression(IdentifierName("System.IntPtr"))
                            )
                        )
                    )
                    .WithSemicolonToken
                    (
                        Token(SyntaxKind.SemicolonToken)
                    )
            )
        );
    }
}
